/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/dict.h>
#include <mc/variant.h>
#include <mc/log.h>
#include <mctp/mctp.h>
#include <algorithm>
#include <regex>

#include "pldm_over_mctp_emulex.h"

namespace dev {


mc::dict get_emulex_command_ctx(uint8_t pldm_type, uint8_t command_code) {
    return mc::dict({
        {"PldmType", pldm_type},
        {"CommandCode", command_code}
    });
}

bool pldm_over_mctp_emulex::send_fru_single_request(uint8_t transfer_operation_flag, uint32_t data_transfer_handle,
                                                   std::vector<fru_tlv_entry_t>& tlv_entries, 
                                                   uint32_t* next_handle, uint8_t* transfer_flag,
                                                   uint16_t* response_record_id) {
    mc::dict ctx = get_emulex_command_ctx(MCTP_PLDM_TYPE_4, MCTP_PLDM_CMD_GET_RECORD_TABLE);
    
    std::vector<uint8_t> req_data(5);
    
    req_data[0] = (data_transfer_handle >> 0) & 0xFF;
    req_data[1] = (data_transfer_handle >> 8) & 0xFF;
    req_data[2] = (data_transfer_handle >> 16) & 0xFF;
    req_data[3] = (data_transfer_handle >> 24) & 0xFF;
    req_data[4] = transfer_operation_flag;
    
    std::string rsp_str;
    
    bool ret = request(ctx, req_data, rsp_str);
    if (!ret || rsp_str.empty()) {
        ilog("Unable to send FRU single request");
        return false;
    }

    // 解析响应
    if (rsp_str.size() < sizeof(pldm_get_fru_record_table_rsp_t)) {
        ilog("Invalid FRU response size: ${size}", ("size", rsp_str.size()));
        return false;
    }

    pldm_get_fru_record_table_rsp_t* rsp = 
        reinterpret_cast<pldm_get_fru_record_table_rsp_t*>(rsp_str.data());
    
    if (!fru_response_is_valid(rsp, rsp_str.size())) {
        return false;
    }

    // 返回record_id
    if (response_record_id) {
        *response_record_id = PLDM_LE16_TO_CPU(rsp->record_id);
    }

    // 填充TLV条目
    fill_fru_table(rsp, rsp_str.size(), tlv_entries);
    
    // 返回传输信息
    if (next_handle) {
        *next_handle = PLDM_LE32_TO_CPU(rsp->next_data_transfer_handle);
    }
    if (transfer_flag) {
        *transfer_flag = rsp->transfer_flag;
    }

    return true;
}

bool pldm_over_mctp_emulex::send_fru_request(uint16_t record_id, std::vector<fru_tlv_entry_t>& tlv_entries) {
    if (record_id != 0xFF && cache_initialized_) {
        std::lock_guard<std::mutex> lock(cache_mutex_);
        auto cache_it = fru_table_cache_.find(record_id);
        if (cache_it != fru_table_cache_.end() && !cache_it->second.empty()) {
            tlv_entries = cache_it->second;
            return true;
        }
        ilog("No cached data for record_id: ${record_id}", ("record_id", record_id));
        
        return false;
    }

    std::map<uint16_t, std::vector<fru_tlv_entry_t>> fru_table;    
    
    uint8_t transfer_operation_flag = MCTP_PLDM_GET_RECORD_TABLE_FIRST_PART;
    uint32_t data_transfer_handle = 0;
    
    for (int i = 0; i < MAX_FRU_TABLE_RECORD_NUM; i++) {
        std::vector<fru_tlv_entry_t> current_tlv_entries;
        uint32_t next_data_transfer_handle = 0;
        uint8_t transfer_flag = 0;
        uint16_t response_record_id = 0;
        
        bool ret = send_fru_single_request(transfer_operation_flag, data_transfer_handle, 
                                         current_tlv_entries, &next_data_transfer_handle, 
                                         &transfer_flag, &response_record_id);
        if (!ret) {
            ilog("Failed to send FRU single request at iteration ${i}", ("i", i));
            return false;
        }
        
        fru_table[response_record_id] = current_tlv_entries;
        ilog("Cached FRU data for record_id: ${record_id} with ${count} TLV entries", 
             ("record_id", response_record_id)("count", current_tlv_entries.size()));
        
        // 检查传输是否完成
        if (transfer_flag == FRU_TABLE_TRANSFER_FLAG_END || 
            transfer_flag == FRU_TABLE_TRANSFER_FLAG_START_END) {
            ilog("FRU table transfer completed");
            break;
        }
        
        data_transfer_handle = next_data_transfer_handle;
        transfer_operation_flag = MCTP_PLDM_GET_RECORD_TABLE_NEXT_PART;
    }

    if(record_id != 0xff){
        auto cache_it = fru_table.find(record_id);
        if (cache_it != fru_table.end() && !cache_it->second.empty()) {
            tlv_entries = cache_it->second;
        }else{
            ilog("Failed to get data for record_id: ${record_id}", ("record_id", record_id));
        }
    }else{
        // 对于record_id=0xFF的请求，返回成功状态（不需要返回具体数据）
        tlv_entries.clear();
    }

    {
        std::lock_guard<std::mutex> lock(cache_mutex_);
        fru_table_cache_ = std::move(fru_table);
    }
    cache_initialized_ = true;
    return true;

}

bool pldm_over_mctp_emulex::update_all_fru_records() {
    std::vector<fru_tlv_entry_t> dummy;
    if (!send_fru_request(0xFF, dummy)) {
        ilog("Failed to refresh FRU table cache");
        return false;
    }
    return true;
}

bool pldm_over_mctp_emulex::get_emulex_fru_info(emulex_fru_info_t* fru_info) {
    if (fru_info == nullptr) {
        return false;
    }


    std::vector<fru_tlv_entry_t> tlv_entries;

    if (send_fru_request(FRU_RECORD_ID_GENERAL, tlv_entries)) {
        for (const auto& tlv : tlv_entries) {
            if (tlv.type == PART_NUMBER_TLV_TYPE) {
                fru_info->part_number = tlv.value;
            } else if (tlv.type == SERIAL_NUMBER_TLV_TYPE) {
                fru_info->serial_number = tlv.value;
            }
        }
    }

    // 获取芯片信息 (record_id = 0x03)
    tlv_entries.clear();
    if (send_fru_request(FRU_RECORD_ID_CHIP, tlv_entries)) {
        for (const auto& tlv : tlv_entries) {
            if (tlv.type == FW_VERSION_TLV_TYPE) {
                std::regex pattern("FW Version:(\\d+\\.?\\d*\\.?\\d*\\.?\\d*)");
                std::smatch matches;
                if (std::regex_search(tlv.value, matches, pattern) && matches.size() > 1) {
                    fru_info->firmware_version = matches[1].str();
                } else {
                    fru_info->firmware_version = "N/A";
                }
            }
        }
    }

    // 获取端口0信息 (record_id = 0x04)
    tlv_entries.clear();
    if (send_fru_request(FRU_RECORD_ID_PORT0, tlv_entries)) {
        for (const auto& tlv : tlv_entries) {
            if (tlv.type == WWNN_TLV_TYPE) {
                fru_info->port0_wwnn = format_wwn(tlv.value, "WWNN");
            } else if (tlv.type == WWPN_TLV_TYPE) {
                fru_info->port0_wwpn = format_wwn(tlv.value, "WWPN");
            }
        }
    }

    // 获取端口1信息 (record_id = 0x05)
    tlv_entries.clear();
    if (send_fru_request(FRU_RECORD_ID_PORT1, tlv_entries)) {
        for (const auto& tlv : tlv_entries) {
            if (tlv.type == WWNN_TLV_TYPE) {
                fru_info->port1_wwnn = format_wwn(tlv.value, "WWNN");
            } else if (tlv.type == WWPN_TLV_TYPE) {
                fru_info->port1_wwpn = format_wwn(tlv.value, "WWPN");
            }
        }
    }

    return true;
}

bool pldm_over_mctp_emulex::fru_response_is_valid(const pldm_get_fru_record_table_rsp_t* rsp, size_t rsp_len) {
    if (rsp == nullptr) {
        return false;
    }
    
    auto record_id = PLDM_LE16_TO_CPU(rsp->record_id);
    auto it = record_tlv_num_emulex.find(record_id);
    if (it == record_tlv_num_emulex.end()) {
        ilog("Invalid record_id: ${record_id}", ("record_id", record_id));
        return false;
    }

    if (rsp->number_field > it->second) {
        ilog("Invalid number_field: ${num} for record_id: ${record_id}", 
             ("num", rsp->number_field)("record_id", record_id));
        return false;
    }

    // 检查数据长度是否足够
    size_t data_start = sizeof(pldm_get_fru_record_table_rsp_t) - 1; 
    size_t required_len = data_start;
    
    // 遍历TLV条目检查长度
    const uint8_t* data_ptr = rsp->data;
    size_t remaining_len = rsp_len - data_start;
    
    for (uint8_t i = 0; i < rsp->number_field; i++) {
        if (remaining_len < 2) { // 至少需要type和length
            return false;
        }
        
        uint8_t tlv_type = data_ptr[0];
        uint8_t tlv_length = data_ptr[1];
        
        if (remaining_len < 2 + tlv_length) {
            return false;
        }
        
        data_ptr += 2 + tlv_length;
        remaining_len -= 2 + tlv_length;
    }

    return true;
}

void pldm_over_mctp_emulex::fill_fru_table(const pldm_get_fru_record_table_rsp_t* rsp, size_t rsp_len, 
                                          std::vector<fru_tlv_entry_t>& tlv_entries) {
    tlv_entries.clear();
    
    const uint8_t* data_ptr = rsp->data;
    size_t remaining_len = rsp_len - (sizeof(pldm_get_fru_record_table_rsp_t) - 1);
    
    for (uint8_t i = 0; i < rsp->number_field; i++) {
        if (remaining_len < 2) {
            break;
        }
        
        fru_tlv_entry_t tlv;
        tlv.type = data_ptr[0];
        tlv.length = data_ptr[1];
        
        if (remaining_len < 2 + tlv.length) {
            break;
        }
        
        if (tlv.length > 0) {
            const char* str_start = reinterpret_cast<const char*>(data_ptr + 2);
            size_t actual_length = strnlen(str_start, tlv.length);
            tlv.value = std::string(str_start, actual_length);
        } else {
            tlv.value = "";
        }
        
        tlv_entries.push_back(tlv);
        
        data_ptr += 2 + tlv.length;
        remaining_len -= 2 + tlv.length;
    }
}

std::string pldm_over_mctp_emulex::format_wwn(const std::string& input_str, const std::string& prefix_type) {
    if (input_str.empty() || prefix_type.empty()) {
        return "";
    }

    std::string expected_prefix = prefix_type + ":";
    if (input_str.substr(0, expected_prefix.length()) != expected_prefix) {
        return "";
    }

    std::string hex_part = input_str.substr(expected_prefix.length());
    if (hex_part.length() != 16) {
        return "";
    }

    // 验证是否为有效的十六进制字符串
    if (!std::all_of(hex_part.begin(), hex_part.end(), [](char c) {
        return std::isxdigit(c);
    })) {
        return "";
    }

    // 格式化输出，每两个字符插入冒号
    std::string formatted_output;
    for (size_t i = 0; i < hex_part.length(); i += 2) {
        if (i > 0) {
            formatted_output += ":";
        }
        formatted_output += hex_part.substr(i, 2);
    }

    return formatted_output;
}

bool pldm_over_mctp_emulex::get_chip_temperature(int16_t* temperature) {
    if (temperature == nullptr) {
        ilog("Invalid temperature pointer");
        return false;
    }

    NumericSensorReading result;
    bool ret = get_sensor_reading(0x21, false, result); // sensor_id = 0x21 for chip temperature
    
    if (!ret) {
        ilog("Failed to get chip temperature");
        *temperature = 0xFF;
        return false;
    }

    // Check if sensor is enabled and data size is correct
    if (result.sensor_operational_state != MCTP_PLDM_SENSOR_OPERATION_STATE_ENABLE) {
        ilog("Chip temperature sensor is not operational");
        *temperature = 0xFF;
        return false;
    }

    if (result.sensor_data_size != 0) { 
        ilog("Invalid sensor data size for chip temperature: ${size}", 
             ("size", result.sensor_data_size));
        *temperature = 0xFF;
        return false;
    }

    try{
        uint8_t temp_val = std::get<uint8_t>(result.reading);
        *temperature = static_cast<int16_t>(temp_val);
        return true;
    } catch (const std::exception& e) {
        ilog("error getting chip temperature: ${error}", ("error", e.what()));
        *temperature = 0xFF;
        return false;
    }
}

bool pldm_over_mctp_emulex::get_link_status(uint8_t port_id, uint8_t* status) {
    if (status == nullptr) {
        ilog("Invalid status pointer");
        return false;
    }


    // sensor ID: port0 = 0x81, port1 = 0x82
    uint16_t sensor_id = 0x81 + port_id;
    
    std::vector<pldm_statesensor_field_t> state_fields;
    bool ret = get_state_sensor_readings(sensor_id, 0, state_fields);
    
    if (!ret || state_fields.empty()) {
        ilog("Failed to get link status for port ${port_id}", ("port_id", port_id));
        *status = 0xFF;
        return false;
    }

    const auto& field = state_fields[0];
    if (field.operational_state != MCTP_PLDM_SENSOR_OPERATION_STATE_ENABLE) {
        ilog("Link status sensor is not operational for port ${port_id}", ("port_id", port_id));
        *status = 0xFF; // Disconnected
        return false;
    }

    // status: 1 = Connected, 0 = Disconnected
    *status = (field.present_state == MCTP_EMULEX_SENSOR_STATE_CONNECT) ? 1 : 0;
    return true;
}

bool pldm_over_mctp_emulex::get_link_speed(uint8_t port_id, uint32_t* speed) {
    if (speed == nullptr) {
        ilog("Invalid speed pointer");
        return false;
    }

    //  sensor ID: port0 = 0x31, port1 = 0x32
    uint16_t sensor_id = 0x31 + port_id;
    
    NumericSensorReading result;
    bool ret = get_sensor_reading(sensor_id, false, result);
    
    if (!ret) {
        ilog("Failed to get link speed for port ${port_id}", ("port_id", port_id));
        *speed = 0xFFFFFFFF;
        return false;
    }

    if (result.sensor_operational_state != MCTP_PLDM_SENSOR_OPERATION_STATE_ENABLE) {
        ilog("Link speed sensor is not operational for port ${port_id}", ("port_id", port_id));
        *speed = 0xFFFFFFFF;
        return false;
    }

    if (result.sensor_data_size != 2) { // link speed is (uint16)
        ilog("Invalid sensor data size for link speed on port ${port_id}: ${size}", 
             ("port_id", port_id)("size", result.sensor_data_size));
        *speed = 0xFFFFFFFF;
        return false;
    }

    try{
        uint16_t speed_val = std::get<uint16_t>(result.reading);
        *speed = static_cast<uint32_t>(speed_val);
        return true;
    }catch (const std::exception& e) {
        ilog("Unexpected error getting link speed for port ${port_id}: ${error}", 
             ("port_id", port_id)("error", e.what()));
        *speed = 0xFFFFFFFF;
        return false;
    }
}

} // namespace dev
